home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Sprite 1984 - 1993
/
Sprite 1984 - 1993.iso
/
src
/
machserver
/
1.098
/
mem
/
memory.c
< prev
next >
Wrap
C/C++ Source or Header
|
1991-04-21
|
46KB
|
1,607 lines
/*
* memory.c --
*
* This file implements a dynamic memory allocation system.
* It provides procedures to allocate and release storage,
* and also includes monitoring and tracing features.
*
* Copyright 1985 Regents of the University of California
* All rights reserved.
*/
#ifndef lint
static char rcsid[] = "$Header: /sprite/src/kernel/mem/RCS/memory.c,v 9.5 90/10/17 11:35:02 rab Exp Locker: mgbaker $ SPRITE (Berkeley)";
#endif /* not lint */
#include <stdlib.h>
#include <mem.h>
#include <memInt.h>
#include <sprite.h>
#include <sync.h>
#include <stdio.h>
#undef free
/*
* If the MEM_TRACE flag is defined, extra code will be compiled to allow
* programs to trace Mem_Alloc and Mem_Free calls.
#define MEM_TRACE
*/
/*
* WARNING: several of the macros in this file expect both integers and
* pointers to be 32 bits (4 bytes) long.
*/
/*
* This storage allocator consists of two independent allocators,
* one used for binned objects (<= BIN_SIZE bytes) and the other
* used for large objects.
*
* Both allocators deal with "blocks" of storage, which correspond
* roughly to the areas that users request and free. Each block of
* storage consists of one administrative word followed by the
* useable area. All allocations are done in even numbers of words,
* which means that clients may occasionally get more bytes than
* they asked for. Mem_Alloc returns the address of the word just
* after the administrative word. The administrative word allows us
* to keep track of which blocks are in use and which are free, and also
* keeps track of the sizes of blocks so clients don't have to know how
* large a block is when they free it. The low-order bit of the
* administrative word indicates whether or not the block is free, and
* the rest of the word tells how large the block is (in bytes
* including the administrative word). The next-to-low-order bit
* of each block is used to indicate that this is a "dummy block"(see
* the comments for the large-object allocator). Using the low-order
* bits for flags works because all blocks that we allocate are always
* multiples of 4 bytes in length. There is one exception to this
* usage of the administrative word, which occurs for free binned objects.
* In this case the word is a link to the next free binned object instead
* of a size. The macros below define the format of the administrative
* words and how to access them.
*
* All objects <= SMALL_SIZE are automatically binned. In order to bin
* objects between SMALL_SIZE and BIN_SIZE bytes the routine Mem_Bin
* should be called. Note that Mem_Bin must be called before any objects
* of the size to be binned have been allocated.
*/
#ifdef MEM_TRACE
typedef struct {
int admin; /* Administrative word. */
Address pc; /* PC of call to Mem_Alloc. */
int origSize; /* Original size given to Mem_Alloc. */
int padding; /* To make it double word aligned */
} AdminInfo;
#define GET_ADMIN(blockPtr) (((AdminInfo *) (blockPtr))->admin)
#define SET_ADMIN(blockPtr, value) \
((AdminInfo *) (blockPtr))->admin = (int) (value)
#define GET_PC(blockPtr) (((AdminInfo *) (blockPtr))->pc)
#define SET_PC(blockPtr) ((AdminInfo *) (blockPtr))->pc = Mem_CallerPC()
#define GET_ORIG_SIZE(blockPtr) (((AdminInfo *) (blockPtr))->origSize)
#define SET_ORIG_SIZE(blockPtr,size) \
((AdminInfo *) (blockPtr))->origSize = size
#else /* MEM_TRACE */
#if defined(sequent)
/*
* Need 16-byte alignment for Sequent Symmetry due to various IO devices
* (eg, SCED needs 8-byte alignment, DCC needs 16-byte). Would be nicer if
* all upper level code doing IO insured the buffers were aligned (hence avoid
* extra fragmentation here), but will live with this. Note that this isn't a
* problem in Sequent Dynix, since all IO buffers are implicitly aligned.
*
* Note that can probably align the bin'd objects differently so that the
* admin word lives just before a 16-byte boundary. This is more than I want
* to modify right now ;-)
*
* This assumes Vm_RawAlloc() returns 16-byte aligned data, as well.
*/
typedef struct {
int admin; /* Administrative word. */
int padding[3]; /* To make it 16-byte aligned */
} AdminInfo;
#define GET_ADMIN(blockPtr) (((AdminInfo *) (blockPtr))->admin)
#define SET_ADMIN(blockPtr, value) \
((AdminInfo *) (blockPtr))->admin = (int) (value)
#else /* sequent */
typedef double AdminInfo;
#define GET_ADMIN(blockPtr) (*(int *)(AdminInfo *) (blockPtr))
#define SET_ADMIN(blockPtr, value) *((int *)(AdminInfo *) (blockPtr)) = (int) (value)
#endif /* sequent */
#endif /* MEM_TRACE */
#define USE_BIT 1
#define DUMMY_BITS 3
#define ADMIN_BITS(admin) ((admin) & DUMMY_BITS)
#define IS_IN_USE(admin) ((admin) & USE_BIT)
#define IS_DUMMY(admin) (((admin) & DUMMY_BITS) == DUMMY_BITS)
#define MARK_DUMMY(admin) ((admin) | DUMMY_BITS)
#define MARK_IN_USE(admin) ((admin) | USE_BIT)
#define MARK_FREE(admin) ((admin) & ~DUMMY_BITS)
#define SIZE(admin) ((admin) & ~DUMMY_BITS)
/*
* The memory manager is a monitor. The following definition is
* for its monitor lock.
*/
static Sync_Lock memMonitorLock = Sync_LockInitStatic("memMonitorLock");
#define LOCKPTR (&memMonitorLock)
static int mem_NumAllocs = 0;
static int mem_NumFrees = 0;
#define MAX_TO_PRINT 256
static struct {
int size; /* Size of the block. */
int num; /* Number of blocks allocated. */
int free; /* Number of blocks freed. */
int inUse; /* Number of blocks still in use. */
int dummy; /* Number of blocks used as dummy blocks. */
} topN[MAX_TO_PRINT + 1];
static void Init _ARGS_((void));
#ifdef MEM_TRACE
INTERNAL static void PrintTrace _ARGS_((Boolean allocated,
register Address infoPtr, Address curPC));
INTERNAL static void DoTrace _ARGS_((Boolean allocated,
register Address infoPtr, Address curPC, int size));
#endif
/*
* ----------------------------------------------------------------------------
*
* Binned-object allocator: it keeps a separate linked free list
* for each size of object less than SMALL_SIZE bytes in size and
* a free list for all objects less than BIN_SIZE that have been
* specified as being binned by a call to Mem_Bin.
* The blocks on each list are linked together via their
* administrative words, terminated by a NIL pointer. Allocation
* and freeing are done in a stack-like manner from the front of
* the free lists.
*
* Definitions (these are all related: if you change one, you'll
* have to change several):
*
* GRANULARITY: The difference in size between succesive buckets. This
* is determined by data alignment restrictions and should be
* a power of 2 to allow for shifting to map from size to bucket.
* SIZE_SHIFT: The shift distance to convert between size and bucket.
* SMALL_SIZE: largest blocksize (total including administrative word)
* managed by default by the binned-object allocator. This
* should be 1 less than a multiple of the granularity.
* BIN_SIZE: largest possible that can be binned. Should be one less
* than a multiple of the granularity.
*
* These can be computed from the above constants.
*
* SMALL_BUCKETS: default number of separate free lists.
* BIN_BUCKETS: number of binned free lists.
* BYTES_TO_BLOCKSIZE: how many bytes a block must contain (total
* including admin. word) to satisfy a user request in bytes.
* BLOCKSIZE_TO_INDEX: which free list to use for blocks of a given
* size (total including administrative word).
* INDEX_TO_BLOCKSIZE: the (maximum) size corresponding to a bucket.
* This will include the size of the administrative word.
*
* These constants determine how many new blocks are allocated to
* a bin at one time.
*
* MIN_BLOCKS_AT_ONCE: The amount allocate the first time, and the amount
* to increment the number of blocks each successive time.
* MIN_SHIFT: The log2 of MIN_BLOCKS_AT_ONCE.
* MAX_BLOCKS_AT_ONCE: the most number of blocks to be allocated at once.
*
* ----------------------------------------------------------------------------
*/
#if defined(sequent)
/*
* 16 byte alignment to for Sequent Symmetry. Making granularity also 16
* bytes has nice cache effects (at least thru Model B; this isn't
* functionally important, but it may affect performance).
*/
#define GRANULARITY 16
#define SIZE_SHIFT 4
/*
* This SMALL_SIZE determined by examination of memory tracing, 1/89.
* The BIN_SIZE allows us to bin an FS_BLOCK_SIZE object.
*/
#define SMALL_SIZE 271
#define BIN_SIZE 4207
#else /* sequent */
/*
* 8 byte granularity to handle SPUR and SPARC.
*/
#define GRANULARITY 8
#define SIZE_SHIFT 3
/*
* This SMALL_SIZE determined by examination of memory tracing, 1/89.
* The BIN_SIZE allows us to bin an FS_BLOCK_SIZE object.
*/
#define SMALL_SIZE 271
#define BIN_SIZE 4199
#endif /* sequent */
#define SMALL_BUCKETS ((SMALL_SIZE + 1) / GRANULARITY)
#define BIN_BUCKETS ((BIN_SIZE + 1) / GRANULARITY)
#define MASK (GRANULARITY - 1)
#define BYTES_TO_BLOCKSIZE(bytes) ((bytes+MASK+sizeof(AdminInfo)) & (~MASK))
#define BLOCKSIZE_TO_INDEX(size) ((size) >> SIZE_SHIFT)
#define INDEX_TO_BLOCKSIZE(index) ((index) << SIZE_SHIFT)
/*
* These are set up to allocate 4, 8, 12, and then 16 blocks at a time.
*/
#define MIN_BLOCKS_AT_ONCE 4
#define MIN_SHIFT 2
#define MAX_BLOCKS_AT_ONCE 16
/*
* The following array holds pointers to the first free blocks of each size.
* Bucket i holds blocks whose total length is INDEX_TO_BLOCKSIZE(i) bytes
* (including the administrative info). Blocks from bucket i are used to
* satisfy user requests ranging from INDEX_TO_BLOCKSIZE(i)-sizeof(AdminInfo)
* bytes up to INDEX_TO_BLOCKSIZE(i)-1 bytes.
* Buckets 0 and 1 are never used, since they correspond to blocks too small
* to hold any information for the user.
*/
static Address freeLists[BIN_BUCKETS];
/*
* Used to gather statistics about allocations:
*/
static int numBlocks[BIN_BUCKETS]; /* Total number of existing blocks,
* both allocated and free, for each
* small size. */
static int numAllocs[BIN_BUCKETS]; /* Total number of allocation requests
* made for blocks of each small size.
*/
/*
* ----------------------------------------------------------------------------
* Large-object allocator: used for blocks that aren't binned.
* We expect there to be relatively few allocations
* made by the large-object allocator. Since all the blocks it
* allocates are relatively large, fragmentation should be less
* than it would be if it allocated small blocks too. All of the
* blocks, in use or not, are kept in a single linked list using
* their administrative words. The address of the next block in
* the list is found by adding the size of the current block to the
* address of its administrative word. Things are complicated
* slightly because the storage area managed by this allocator
* may be discontiguous: there could be several large regions
* that it manages, separated by gaps that are managed by some other
* storage allocator (e.g. the binned-object allocator). In this
* case, the gaps between regions are handled by making the gaps
* look like allocated blocks, with an administrative word at the
* end of one region that causes a skip to the beginning of the
* next region. These blocks are called "dummy blocks", and have
* special flag bits in their administrative words (see definitions
* below). All the blocks within a region are linked together
* in increasing order of address. However, the different regions
* may appear in any order on the list. Two dummy blocks mark the
* beginning and end of the list.
* ----------------------------------------------------------------------------
*/
static char *first, *last;
static char _first[sizeof(AdminInfo) + 4], _last[sizeof(AdminInfo) + 4];
/* Dummy blocks marking beginning and
* end of free list. Last has a size of
* zero. */
static Address currentPtr; /* Next block to consider allocating.
* Rotates circularly through free list. */
int largestFree; /* This holds the size of the largest
* free block that's been seen on the
* free list. It's updated as the
* list is traversed. */
/*
* Minimum size of new regions requested by large-object allocator
* when storage runs out:
*/
#define MIN_REGION_SIZE 2048
static int numLargeBytes; /* Total amount of storage managed by
* the large allocator. */
static int numLargeAllocs; /* Total number of allocation requests
* handled by the large allocator. */
static int numLargeLoops; /* Number of iterations through the
* inner block-checking loop of the
* large allocator. */
/*
* ----------------------------------------------------------------------------
* Miscellaneous declarations.
* ----------------------------------------------------------------------------
*/
/*
* Flag to make sure Init gets called once.
*/
static void Init _ARGS_((void));
static int initialized = FALSE;
#ifdef MEM_TRACE
/*
* When Mem_Alloc or Mem_Free is called, a trace record will be printed and/or
* the number of blocks being used by the caller will be stored in the trace
* array if the block size is in sizeTraceArray. The array is defined with
* Mem_SetTraceSizes(). PrintTrace calls a default printing routine; it can
* be changed with Mem_SetPrintProc().
*/
#define MAX_NUM_TRACE_SIZES 20
#define MAX_CALLERS_TO_TRACE 20
static struct TraceElement {
Mem_TraceInfo traceInfo;
struct {
Address callerPC;
int numBlocks;
} allocInfo[MAX_CALLERS_TO_TRACE];
} sizeTraceArray[MAX_NUM_TRACE_SIZES];
static int numSizesToTrace = 0;
int mem_PrintLargeAllocPC = 0;
#endif /* MEM_TRACE */
/*
* ----------------------------------------------------------------------------
*
* Init --
*
* Initializes the dynamic storage allocator.
*
* Results:
* None.
*
* Side effects:
* The storage allocation structures are initialized.
*
* ----------------------------------------------------------------------------
*/
INTERNAL static void
Init()
{
int i;
/*
* Clear out all of the bins.
*/
for (i = BIN_BUCKETS - 1; i > SMALL_BUCKETS - 1; i--) {
numBlocks[i] = -1;
freeLists[i] = (Address) NIL;
numAllocs[i] = 0;
}
/*
* Clear out the small-object free lists.
*/
for (; i >= 0; i--) {
freeLists[i] = (Address) NIL;
numBlocks[i] = 0;
numAllocs[i] = 0;
}
/*
* Initialize the large-object free list with two dummy blocks that
* mark its beginning and end. These blocks are declared to be arrays
* of characters. In order for malloc to work correctly they have to
* be aligned on word boundaries.
*/
#if 0
if (((int) first) & 0x3) {
panic("Mem: 'first' is not word-aligned");
}
if (((int) last) & 0x3) {
panic("Mem: 'last' is not word-aligned");
}
#else
first = (char *) (((long)_first + 3) & ~3);
last = (char *) (((long)_last + 3) & ~3);
#endif
SET_ADMIN(first, MARK_DUMMY(last-first));
SET_ADMIN(last, MARK_DUMMY(0));
currentPtr = first;
largestFree = 0;
numLargeBytes = 0;
numLargeAllocs = 0;
numLargeLoops = 0;
MemPrintInit();
}
/*
* ----------------------------------------------------------------------------
*
* Mem_Bin --
*
* Make objects of the given size be binned.
*
* Results:
* None.
*
* Side effects:
* The bin corresponding to blocks of the given size is initialized.
*
* ----------------------------------------------------------------------------
*/
ENTRY void
Mem_Bin(numBytes)
int numBytes;
{
int index;
LOCK_MONITOR;
if (!initialized) {
initialized = TRUE;
Init();
}
if (mem_NumAllocs > 0) {
MemPanic("Mem_Bin: Mem_Alloc already called.\n");
}
numBytes = BYTES_TO_BLOCKSIZE(numBytes);
if (numBytes > BIN_SIZE) {
UNLOCK_MONITOR;
return;
}
index = BLOCKSIZE_TO_INDEX(numBytes);
if (numBlocks[index] >= 0) {
UNLOCK_MONITOR;
return;
}
numBlocks[index] = 0;
UNLOCK_MONITOR;
}
/*
* ----------------------------------------------------------------------------
*
* malloc --
*
* This procedure allocates a chunk of memory.
*
* Results:
* The return value is a pointer to an area of at least numBytes
* bytes of free storage. If no memory is available, then this
* procedure will never return: the MemChunkAlloc procedure
* determines what will happen.
*
* Side effects:
* The returned block is marked as allocated and will not be
* allocated to anyone else until it is returned with a call
* to free().
*
* ----------------------------------------------------------------------------
*/
ENTRY Address
malloc(numBytes)
register unsigned int numBytes; /* How many bytes to allocate.
* Must be > 0 */
{
register int size, admin;
Address result;
Address newRegion;
int regionSize;
#ifdef MEM_TRACE
int origSize;
#endif /* MEM_TRACE */
register int index;
LOCK_MONITOR;
mem_NumAllocs++;
if (!initialized) {
initialized = TRUE;
Init();
}
#ifdef MEM_TRACE
origSize = numBytes;
#endif /* MEM_TRACE */
/*
* Handle binned objects quickly, if possible.
*/
numBytes = BYTES_TO_BLOCKSIZE(numBytes);
index = BLOCKSIZE_TO_INDEX(numBytes);
if (numBytes <= BIN_SIZE && numBlocks[index] != -1) {
if (freeLists[index] == (Address) NIL) {
register int blocksAtOnce;
/*
* There aren't currently any free objects of this size.
* Call the client's function to obtain another region
* from the system. While we're at it, get a whole bunch
* of objects of this size once and put all but one back
* on the free list.
*/
if (numBlocks[index] < MAX_BLOCKS_AT_ONCE) {
blocksAtOnce = ((numBlocks[index] >> MIN_SHIFT) + 1) *
MIN_BLOCKS_AT_ONCE;
} else {
blocksAtOnce = MAX_BLOCKS_AT_ONCE;
}
regionSize = MemChunkAlloc(blocksAtOnce * numBytes, &newRegion);
while (regionSize >= numBytes) {
SET_ADMIN(newRegion, freeLists[index]);
freeLists[index] = newRegion;
numBlocks[index] += 1;
newRegion += numBytes;
regionSize -= numBytes;
}
/*
* If we were given more bytes than we wanted, and there aren't
* quite enough left to make a full block, put the leftovers
* on the free list for the smallest size. This may still result
* in GRANULARITY bytes leftover that we have to throw away.
*/
while (regionSize >= (sizeof(AdminInfo) + GRANULARITY)) {
SET_ADMIN(newRegion, freeLists[2]);
freeLists[2] = newRegion;
numBlocks[2] += 1;
newRegion += (sizeof(AdminInfo) + GRANULARITY);
regionSize -= (sizeof(AdminInfo) + GRANULARITY);
}
}
result = freeLists[index];
freeLists[index] = (Address) GET_ADMIN(result);
SET_ADMIN(result, MARK_IN_USE(numBytes));
numAllocs[index] += 1;
#ifdef MEM_TRACE
SET_PC(result);
SET_ORIG_SIZE(result, origSize);
DoTrace(TRUE, result, (Address)NULL, numBytes);
#endif /* MEM_TRACE */
UNLOCK_MONITOR;
return(result+sizeof(AdminInfo));
}
/*
* This is a large object. Step circularly through the blocks
* in the list, looking for one that's large enough to hold
* what's needed. Move currentPtr to the next block in the list to
* make sure that we make forward progress each time Mem_Alloc is called.
* If we don't then there is an instability in the memory allocator
* with the following pattern:
*
* while (TRUE) {
* addr = malloc(4096);
* free(addr);
* malloc(248);
* }
*
* This pattern causes memory to get heavily fragmented.
*/
numLargeAllocs += 1;
admin = GET_ADMIN(currentPtr);
currentPtr += SIZE(admin);
while (TRUE) {
Address nextPtr;
numLargeLoops += 1;
admin = GET_ADMIN(currentPtr);
size = SIZE(admin);
nextPtr = currentPtr+size;
if (!IS_IN_USE(admin)) {
/*
* Several blocks in a row could have been freed since the last
* time we were here. If so, merge them together.
*/
while (!IS_IN_USE(GET_ADMIN(nextPtr))) {
size += SIZE(GET_ADMIN(nextPtr));
admin = MARK_FREE(size);
SET_ADMIN(currentPtr, admin);
nextPtr = currentPtr+size;
}
if (size >= numBytes) {
break;
}
if (size > largestFree) {
largestFree = size;
}
}
/*
* This block won't do the job. Go on to the next block.
* If the next block is the end of the list, then go back
* to the beginning and start again, unless no storage has
* been freed since the last time we went back. In this
* case, allocate a new region of storage.
*/
if (nextPtr != last) {
currentPtr = nextPtr;
continue;
}
if (largestFree > numBytes) {
largestFree = 0;
currentPtr = first;
continue;
}
if (numBytes < MIN_REGION_SIZE) {
regionSize = MemChunkAlloc(MIN_REGION_SIZE, &newRegion);
} else {
regionSize = MemChunkAlloc(numBytes + sizeof(AdminInfo),
&newRegion);
}
numLargeBytes += regionSize;
/*
* If the new region immediately follows the end of the previous
* region, merge the two together (at this point currentPtr always
* points to a dummy block whose administrative word points to "last").
* If we can't merge, then make currentPtr link to the new area.
*/
if (newRegion == (currentPtr+sizeof(AdminInfo))) {
newRegion = currentPtr;
regionSize += sizeof(AdminInfo);
} else {
SET_ADMIN(currentPtr, MARK_DUMMY(newRegion - currentPtr));
}
/*
* Create a dummy block at the end of the new region, which links
* to "last".
*/
SET_ADMIN(newRegion, MARK_FREE(regionSize - sizeof(AdminInfo)));
newRegion += regionSize - sizeof(AdminInfo);
SET_ADMIN(newRegion, MARK_DUMMY(last - newRegion));
/*
* Continue scanning the list (try currentPtr again in case it
* merged with the new region).
*/
}
/*
* At this point we've got a block that's large enough. If it's
* larger than needed for the object, put the rest back on the
* free list (note: even if the remnant is smaller than the smallest
* large object, which means it'll be used by itself, we put it back
* on the list so it can merge with either this block or the next,
* whichever gets freed first).
*/
if (size == numBytes) {
SET_ADMIN(currentPtr, MARK_IN_USE(admin));
} else {
SET_ADMIN(currentPtr+numBytes, MARK_FREE(size-numBytes));
SET_ADMIN(currentPtr, MARK_IN_USE(numBytes));
}
#ifdef MEM_TRACE
SET_PC(currentPtr);
SET_ORIG_SIZE(currentPtr, origSize);
DoTrace(TRUE, currentPtr, (Address)NULL, numBytes);
#endif /* MEM_TRACE */
result = currentPtr;
UNLOCK_MONITOR;
return(result+sizeof(AdminInfo));
}
/*
* ----------------------------------------------------------------------------
*
* _free --
*
* Return a previously-allocated block of storage to the free pool.
*
* Results:
* None.
*
* Side effects:
* The storage pointed to by blockPtr is marked as free and returned
* to the free pool. Nothing in the bytes pointed to by blockPtr is
* modified at this time: no change will occur until at least the
* next call to Mem_Alloc. This means that callers may use the contents
* of a block for a short time after free-ing it (e.g. to read a
* "next" pointer).
*
* ----------------------------------------------------------------------------
*/
ENTRY int
free(blockPtr)
Address blockPtr;
{
return _free(blockPtr);
}
ENTRY int
_free(blockPtr)
register Address blockPtr; /* Pointer to storage to be freed. Must
* have been the return value from Mem_Alloc
* at some previous time. */
{
register int admin;
register int index;
register int size;
LOCK_MONITOR;
mem_NumFrees++;
if (!initialized) {
MemPanic("Mem_Free: allocator not initialized!\n");
return 0; /* should never get here */
}
/*
* Make sure that this block bears some resemblance to a
* well-formed storage block.
*/
blockPtr -= sizeof(AdminInfo);
admin = GET_ADMIN(blockPtr);
if (ADMIN_BITS(admin) != USE_BIT) {
if (!IS_IN_USE(admin)) {
if (!memAllowFreeingFree) {
MemPanic("Mem_Free: storage block already free\n");
}
} else {
MemPanic("Mem_Free: storage block is corrupted\n");
}
UNLOCK_MONITOR;
return 0; /* (should never get here) */
}
/* This procedure is easier for large blocks than for small ones.
* If large, just clear the use bit. Since this block could merge
* with its neighbors, we don't really know anymore what's the
* largest size on the free list, so set largestFree to a very
* large number. This guarantees that the allocator will check
* the whole list again before requesting additional memory.
*/
size = SIZE(admin);
index = BLOCKSIZE_TO_INDEX(size);
if (size > BIN_SIZE || numBlocks[index] == -1) {
SET_ADMIN(blockPtr, MARK_FREE(admin));
largestFree = numLargeBytes;
} else {
/*
* For small blocks, add the block back onto its free list.
*/
SET_ADMIN(blockPtr, freeLists[index]);
freeLists[index] = blockPtr;
}
#ifdef MEM_TRACE
DoTrace(FALSE, blockPtr, Mem_CallerPC(), size);
#endif /* MEM_TRACE */
UNLOCK_MONITOR;
return 0;
}
/*
* Global variables that can be set to control thresholds for printing
* statistics.
*/
static int mem_SmallMinNum = 1; /* There must be at least this many
* binned objects of a size before info
* about its size gets printed. */
static int mem_LargeMinNum = 1; /* There must be at least this many
* non-binned objects of a size before
* info about the size gets printed. */
static int mem_LargeMaxSize = 10000; /* Info is printed for non-binned
* objects larger than this regardless
* of how many of them there are. */
/*
* ----------------------------------------------------------------------------
*
* Mem_Size --
*
* Return the size of a previously-allocated storage block.
*
* Results:
* The return value is the size of *blockPtr, in bytes. This is
* the total usable size of the block. It may be slightly greater
* than the size actually requested in the Mem_Alloc, since this
* module rounds sizes up to convenient boundaries.
*
* Side effects:
* None.
*
* ----------------------------------------------------------------------------
*/
ENTRY int
Mem_Size(blockPtr)
Address blockPtr; /* Pointer to storage to be freed. Must have been the
* return value from Mem_Alloc at some previous time. */
{
int admin;
LOCK_MONITOR;
if (!initialized) {
MemPanic("Mem_Size: allocator not initialized!\n");
UNLOCK_MONITOR;
return(0); /* should never get here */
}
/*
* Make sure that this block bears some resemblance to a
* well-formed storage block.
*/
blockPtr -= sizeof(AdminInfo);
admin = GET_ADMIN(blockPtr);
if (ADMIN_BITS(admin) != USE_BIT) {
if (!IS_IN_USE(admin)) {
MemPanic("Mem_Size: storage block is free\n");
} else {
MemPanic("Mem_Size: storage block is corrupted\n");
}
UNLOCK_MONITOR;
return(0); /* (should never get here) */
}
UNLOCK_MONITOR;
return(SIZE(admin) - sizeof(AdminInfo));
}
/*
*----------------------------------------------------------------------
*
* Mem_PrintStats --
*
* Print out memory statistics with Mem_PrintStatsSubr using the
* default printing routine and default sizes.
*
* See Mem_PrintStatsSubrInt for details.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
ENTRY void
Mem_PrintStats()
{
LOCK_MONITOR;
Mem_PrintStatsSubrInt(memPrintProc, memPrintData, mem_SmallMinNum,
mem_LargeMinNum, mem_LargeMaxSize);
UNLOCK_MONITOR;
}
/*
*----------------------------------------------------------------------
*
* Mem_PrintStatsInt --
*
* Print out memory statistics with Mem_PrintStatsSubr using the
* default printing routine and default sizes. Same as Mem_PrintStats
* except that this routine does not grab the monitor lock. This is
* intended to be used to print out memory stats when the operating
* system runs out of memory after being called by Mem_Alloc and hence
* the monitor is already down.
*
* See Mem_PrintStatsSubrInt for details.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
void
Mem_PrintStatsInt()
{
Mem_PrintStatsSubrInt(memPrintProc, memPrintData, mem_SmallMinNum,
mem_LargeMinNum, mem_LargeMaxSize);
}
/*
* ----------------------------------------------------------------------------
*
* Mem_PrintStatsSubr --
*
* This procedure prints out statistics about memory allocation
* so far. Grabs monitor lock and calls the unmonitored routine
* Mem_PrintStatsSubrInt.
*
* Results:
* None.
*
* Side effects:
* None.
*
* ----------------------------------------------------------------------------
*/
ENTRY void
Mem_PrintStatsSubr(printProc, clientData, smallMinNum, largeMinNum,
largeMaxSize)
void (*printProc)(); /* Procedure to actually print stuff. */
ClientData clientData; /* Argument to pass to printProc. */
int smallMinNum; /* Minimum number of elements in a bucket
* before will print out stats. */
int largeMinNum; /* Minimum number of large objects of a given
* size before will print out stats about
* the size. */
int largeMaxSize; /* Statistics will be printed about all blocks
* over size regardless of how many there are*/
{
LOCK_MONITOR;
Mem_PrintStatsSubrInt(printProc, clientData, smallMinNum, largeMinNum,
largeMaxSize);
UNLOCK_MONITOR;
}
/*
* ----------------------------------------------------------------------------
*
* Mem_PrintConfig --
*
* Prints out the exact configuration of the dynamic memory allocator
* using the default printing routine.
*
* Results:
* None.
*
* Side effects:
* None.
*
* ----------------------------------------------------------------------------
*/
void
Mem_PrintConfig()
{
Mem_PrintConfigSubr(memPrintProc, memPrintData);
}
/*
* ----------------------------------------------------------------------------
*
* Mem_PrintConfigSubr --
*
* Uses a client-supplied procedure to print out the exact
* configuration of the dynamic memory allocator.
*
* Results:
* None.
*
* Side effects:
* Stuff gets printed (?) by passing it to printProc. See the
* documentation in Mem_PrintStatsSubr for the calling sequence to
* printProc.
*
* ----------------------------------------------------------------------------
*/
ENTRY void
Mem_PrintConfigSubr(printProc, clientData)
void (*printProc)(); /* Procedure to actually print stuff. */
ClientData clientData; /* Argument to pass to printProc. */
{
register Address ptr;
#ifdef VERBOSE
int i, j;
#endif /* VERBOSE */
LOCK_MONITOR;
if (!initialized) {
(*printProc)(clientData, "Allocator not initialized yet.\n");
return;
}
#ifdef VERBOSE
(*printProc)(clientData, "Small object allocator:\n");
for (i = 2; i < BIN_BUCKETS; i++) {
if (freeLists[i] == (Address) NIL) {
continue;
}
(*printProc)(clientData, " %d bytes:", i*GRANULARITY);
j = 5;
for (ptr = freeLists[i]; ptr != (Address) NIL;
ptr = (Address) GET_ADMIN(ptr)) {
if (j == 5) {
(*printProc)(clientData, "\n ");
j = 0;
} else {
j += 1;
}
(*printProc)(clientData, "%12#x", ptr);
}
(*printProc)(clientData, "\n");
}
#endif /* VERBOSE */
(*printProc)(clientData, "Large object allocator:\n");
#ifdef MEM_TRACE
(*printProc)(clientData, " Location Orig. Size State\n");
#else
(*printProc)(clientData, " Location Size State\n");
#endif /* MEM_TRACE */
for (ptr = first + SIZE(GET_ADMIN(first)); ptr != last;
ptr += SIZE(GET_ADMIN(ptr))) {
#ifdef MEM_TRACE
(*printProc)(clientData, "%12#x %10d", ptr, GET_ORIG_SIZE(ptr));
if (IS_DUMMY(GET_ADMIN(ptr))) {
(*printProc)(clientData, " Dummy\n");
} else if (IS_IN_USE(GET_ADMIN(ptr))) {
(*printProc)(clientData, " In use (PC=0x%x)\n", GET_PC(ptr));
#else
(*printProc)(clientData, "%12#x %10d", ptr, SIZE(GET_ADMIN(ptr)));
if (IS_DUMMY(GET_ADMIN(ptr))) {
(*printProc)(clientData, " Dummy\n");
} else if (IS_IN_USE(GET_ADMIN(ptr))) {
(*printProc)(clientData, " In use\n");
#endif /* MEM_TRACE */
} else {
(*printProc)(clientData, " Free\n");
}
}
UNLOCK_MONITOR;
}
/*
* ----------------------------------------------------------------------------
*
* Mem_PrintInUse --
*
* Uses a default procedure to print out the blocks in allocator
* that are still in use. The original size and the PC of the
* call to Mem_Alloc are printed.
*
* Results:
* None.
*
* Side effects:
* Stuff gets printed (?) by passing it to memPrintProc. See the
* documentation in Mem_PrintStatsSubr for the calling sequence to
* memPrintProc.
*
* ----------------------------------------------------------------------------
*/
ENTRY void
Mem_PrintInUse()
{
#ifdef MEM_TRACE
register Address ptr;
LOCK_MONITOR;
if (!initialized) {
(*memPrintProc)(memPrintData, "Allocator not initialized yet.\n");
return;
}
(*memPrintProc)(memPrintData, "Large objects still in use:\n");
(*memPrintProc)(memPrintData, " Location Orig. Size Alloc.PC\n");
for (ptr = first + SIZE(GET_ADMIN(first)); ptr != last;
ptr += SIZE(GET_ADMIN(ptr))) {
if (!IS_DUMMY(GET_ADMIN(ptr)) && IS_IN_USE(GET_ADMIN(ptr))) {
(*memPrintProc)(memPrintData,"%12#x %10d %12#x\n",
ptr, GET_ORIG_SIZE(ptr), GET_PC(ptr));
}
}
UNLOCK_MONITOR;
#endif /* MEM_TRACE */
}
/*
*----------------------------------------------------------------------
*
* Mem_SetTraceSizes --
*
* Defines a list of sizes to trace and causes tracing to start.
* If the numSizes is zero or the array ptr is NULL, tracing is
* turned off.
*
* Results:
* None.
*
* Side effects:
* Traces of Mem_Alloc and Mem_Free start or end.
*
*----------------------------------------------------------------------
*/
/*ARGSUSED*/
ENTRY void
Mem_SetTraceSizes(numSizes, arrayPtr)
int numSizes; /* # of elements in arrayPtr */
Mem_TraceInfo *arrayPtr; /* Array of block sizes to trace. */
{
#ifdef MEM_TRACE
int i;
LOCK_MONITOR;
if (numSizes <= 0 || arrayPtr == (Mem_TraceInfo *)NIL ||
arrayPtr == (Mem_TraceInfo *)NULL) {
if (numSizes == -1) {
numSizesToTrace = -1;
} else {
numSizesToTrace = 0;
}
UNLOCK_MONITOR;
return;
}
if (numSizes > MAX_NUM_TRACE_SIZES) {
numSizes = MAX_NUM_TRACE_SIZES;
}
numSizesToTrace = numSizes;
for (i = 0; i < numSizes; i++) {
sizeTraceArray[i].traceInfo = arrayPtr[i];
sizeTraceArray[i].traceInfo.flags |= MEM_TRACE_NOT_INIT;
}
UNLOCK_MONITOR;
#endif /* MEM_TRACE */
}
/*
*----------------------------------------------------------------------
*
* Mem_SetPrintProc --
*
* Changes the default printing routines
*
* Results:
* None.
*
* Side effects:
* The default printing routine is changed.
*
*----------------------------------------------------------------------
*/
ENTRY void
Mem_SetPrintProc(proc, data)
void (*proc)(); /* Address of new print routine. */
ClientData data; /* Data to be passed to proc. */
{
LOCK_MONITOR;
if (proc != (void(*)())NIL &&
proc != (void(*)()) NULL) {
memPrintProc = proc;
memPrintData = data;
}
UNLOCK_MONITOR;
}
/*
* ----------------------------------------------------------------------------
*
* Mem_PrintStatsSubrInt --
*
* This procedure prints out statistics about memory allocation
* so far.
*
* Results:
* None.
*
* Side effects:
* The procedure printProc is called several times to print out
* information. Its calling sequence is:
*
* void
* printProc(clientData, format, arg1, arg2, arg3, arg4, arg5)
* ClientData clientData;
* char *format;
*
* This is identical to the calling sequence for the standard I/o
* routine Io_Print. The first argument is the clientData argument
* passed to this procedure, which need not necessarily be a
* (Io_Stream *). There will never be more than 5 arguments.
* A typical calling sequence is:
* "Mem_PrintStatsSubr(Io_PrintStream, (ClientData) io_StdOut,
* 50, 100, 1000)".
*
* ----------------------------------------------------------------------------
*/
INTERNAL void
Mem_PrintStatsSubrInt(printProc, clientData, smallMinNum, largeMinNum,
largeMaxSize)
void (*printProc)(); /* Procedure to actually print stuff. */
ClientData clientData; /* Argument to pass to printProc. */
int smallMinNum; /* Minimum number of elements in a bucket
* before will print out stats. */
int largeMinNum; /* Minimum number of large objects of a given
* size before will print out stats about
* the size. */
int largeMaxSize; /* Statistics will be printed about all blocks
* over size regardless of how many there are*/
{
register Address ptr;
register int i;
int totalBlocks, totalAllocs, totalFree;
int allocBytes = 0;
int freeBytes = 0;
Boolean firstTime = TRUE;
Boolean warnedAboutOverflow;
if (!initialized) {
(*printProc)(clientData, "Allocator not initialized yet.\n");
return;
}
(*printProc)(clientData, "\nTotal allocs = %d, frees = %d\n\n",
mem_NumAllocs, mem_NumFrees);
(*printProc)(clientData, "Small object allocator:\n");
totalBlocks = totalAllocs = totalFree = 0;
for (i = 2; i < BIN_BUCKETS; i++) {
int numFree = 0;
if (numBlocks[i] <= 0) {
continue;
}
allocBytes += numBlocks[i] * INDEX_TO_BLOCKSIZE(i);
for (ptr = freeLists[i];
ptr != (Address) NIL;
ptr = (Address) GET_ADMIN(ptr)) {
freeBytes += INDEX_TO_BLOCKSIZE(i);
numFree += 1;
}
if (numBlocks[i] >= smallMinNum) {
if (firstTime) {
(*printProc)(clientData,
" Size Total Allocs In Use\n");
firstTime = FALSE;
}
(*printProc)(clientData, "%8d%10d%10d%10d\n",
INDEX_TO_BLOCKSIZE(i), numBlocks[i],
numAllocs[i], numBlocks[i] - numFree);
}
totalBlocks += numBlocks[i];
totalAllocs += numAllocs[i];
totalFree += numFree;
}
(*printProc)(clientData, " Total%10d%10d%10d\n", totalBlocks,
totalAllocs, totalBlocks - totalFree);
(*printProc)(clientData, "Bytes allocated = %d, freed = %d\n\n",
allocBytes, freeBytes);
/*
* Initialize the largest N-sizes buffer.
*/
for (i = 0; i < MAX_TO_PRINT + 1; i++) {
topN[i].size = -1;
topN[i].free = 0;
topN[i].dummy = 0;
}
warnedAboutOverflow = FALSE;
for (ptr = first + SIZE(GET_ADMIN(first));
ptr != last;
ptr += SIZE(GET_ADMIN(ptr))) {
int admin;
int size;
Boolean found;
admin = GET_ADMIN(ptr);
if (!IS_DUMMY(admin) && !IS_IN_USE(admin)) {
freeBytes += SIZE(admin);
}
size = SIZE(admin);
#ifdef MEM_TRACE
if (size - GET_ORIG_SIZE(ptr) < 4 &&
size - GET_ORIG_SIZE(ptr) >= 0) {
size = GET_ORIG_SIZE(ptr);
}
#endif /* MEM_TRACE */
found = FALSE;
for (i = 0; i < MAX_TO_PRINT; i++) {
if (size == topN[i].size) {
found = TRUE;
topN[i].num++;
if (IS_DUMMY(admin)) {
topN[i].dummy++;
} else if (IS_IN_USE(admin)) {
topN[i].inUse++;
} else {
topN[i].free++;
}
break;
} else if (topN[i].size == -1) {
found = TRUE;
topN[i].size = size;
topN[i].num = 1;
if (IS_DUMMY(admin)) {
topN[i].dummy = 1;
} else if (IS_IN_USE(admin)) {
topN[i].inUse = 1;
} else {
topN[i].free = 1;
}
break;
}
}
if (!found && !warnedAboutOverflow) {
(*printProc)(clientData, "Largest-Size buffer overflow: %d\n",size);
warnedAboutOverflow = TRUE;
}
}
(*printProc)(clientData, "Large object allocator:\n");
(*printProc)(clientData, " Total bytes managed: %d\n", numLargeBytes);
(*printProc)(clientData, " Bytes in use: %d\n",
numLargeBytes - freeBytes);
firstTime = TRUE;
for (i = 0; topN[i].size != -1; i++) {
if ((topN[i].num >= largeMinNum || topN[i].size >= largeMaxSize) &&
(topN[i].num != topN[i].dummy)) {
if (firstTime) {
(*printProc)(clientData, "%10s%10s%10s%10s\n",
#ifdef MEM_TRACE
"Orig. Size", "Num", "Free", "In Use");
#else
"Size", "Num", "Free", "In Use");
#endif /* MEM_TRACE */
firstTime = FALSE;
}
(*printProc)(clientData, "%10d%10d%10d%10d\n",
topN[i].size, topN[i].num, topN[i].free, topN[i].inUse);
}
}
(*printProc)(clientData, "\n");
}
/*
*----------------------------------------------------------------------
*
* DoTrace --
*
* Print and/or store a trace record.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
#ifdef MEM_TRACE
INTERNAL static void
DoTrace(allocated, infoPtr, curPC, size)
Boolean allocated; /* If TRUE, we are called by Mem_Alloc,
* otherwise by Mem_Free. */
register Address infoPtr; /* Address of admin. info. */
Address curPC; /* If called by Mem_Free, PC of
* call to Mem_Free, NULL otherwise. */
int size; /* Size actually allocated. */
{
int i, j;
int origSize;
Address callerPC;
register struct TraceElement *trPtr;
if (numSizesToTrace == -1) {
PrintTrace(allocated, infoPtr, curPC);
return;
}
callerPC = GET_PC(infoPtr);
origSize = GET_ORIG_SIZE(infoPtr);
for (i = 0, trPtr = sizeTraceArray; i < numSizesToTrace; i++, trPtr++) {
if (trPtr->traceInfo.flags & MEM_DONT_USE_ORIG_SIZE) {
if (trPtr->traceInfo.size != size) {
continue;
}
} else if (trPtr->traceInfo.size != origSize) {
continue;
}
if (trPtr->traceInfo.flags & MEM_PRINT_TRACE) {
PrintTrace(allocated, infoPtr, curPC);
}
if (trPtr->traceInfo.flags & MEM_STORE_TRACE) {
register int slot;
if (trPtr->traceInfo.flags & MEM_TRACE_NOT_INIT) {
for (j = 0; j < MAX_CALLERS_TO_TRACE; j++) {
trPtr->allocInfo[j].numBlocks = 0;
trPtr->allocInfo[j].callerPC = 0;
}
trPtr->traceInfo.flags &= ~MEM_TRACE_NOT_INIT;
}
slot = -1;
for (j = 0; j < MAX_CALLERS_TO_TRACE; j++) {
if (trPtr->allocInfo[j].callerPC == callerPC) {
if (allocated) {
trPtr->allocInfo[j].numBlocks++;
} else {
trPtr->allocInfo[j].numBlocks--;
}
return;
}
if ((trPtr->allocInfo[j].numBlocks == 0) && (slot < 0)) {
slot = j;
}
}
if ((slot >= 0) && allocated) {
trPtr->allocInfo[slot].callerPC = callerPC;
trPtr->allocInfo[slot].numBlocks = 1;
}
}
return;
}
if (size > BIN_SIZE && mem_PrintLargeAllocPC) {
printf("malloc(%d[%d]) from PC 0x%x\n", origSize, size, callerPC);
}
}
#endif /* MEM_TRACE */
/*
*----------------------------------------------------------------------
*
* Mem_DumpTrace --
*
* Dump the allocation trace records for the given size block. If
* the size is specified as -1 then all trace records for all size
* blocks are dumped.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
/*ARGSUSED*/
void
Mem_DumpTrace(blockSize)
int blockSize;
{
#ifdef MEM_TRACE
register struct TraceElement *trPtr;
int i, j;
for (i = 0, trPtr = sizeTraceArray; i < numSizesToTrace; i++, trPtr++) {
if (trPtr->traceInfo.size != blockSize && blockSize != -1) {
continue;
}
if (!(trPtr->traceInfo.flags & MEM_STORE_TRACE) ||
(trPtr->traceInfo.flags & MEM_TRACE_NOT_INIT)) {
continue;
}
if (trPtr->traceInfo.flags & MEM_DONT_USE_ORIG_SIZE) {
(*memPrintProc)(memPrintData, "Trace for block size = %d:\n",
trPtr->traceInfo.size);
} else {
(*memPrintProc)(memPrintData, "Trace for size = %d (%d):\n",
trPtr->traceInfo.size,
BYTES_TO_BLOCKSIZE(trPtr->traceInfo.size));
}
(*memPrintProc)(memPrintData, "Caller-PC Num-blocks \n");
for (j = 0; j < MAX_CALLERS_TO_TRACE; j++) {
if (trPtr->allocInfo[j].numBlocks == 0) {
break;
}
(*memPrintProc)(memPrintData, "%8x %6d\n",
trPtr->allocInfo[j].callerPC,
trPtr->allocInfo[j].numBlocks);
}
if (blockSize != -1) {
break;
}
}
#endif
}
/*
*----------------------------------------------------------------------
*
* PrintTrace --
*
* Print out the given trace information about a memory trace record.
*
* Results:
* None.
*
* Side effects:
* None.
*
*----------------------------------------------------------------------
*/
#ifdef MEM_TRACE
INTERNAL static void
PrintTrace(allocated, infoPtr, curPC)
Boolean allocated; /* If TRUE, we are called by Mem_Alloc,
* otherwise by Mem_Free. */
register Address infoPtr; /* Address of admin. info. */
Address curPC; /* If called by Mem_Free, PC of
* call to Mem_Free, NULL otherwise. */
{
if (allocated) {
(*memPrintProc)(memPrintData,
"Mem_Alloc: PC=0x%x addr=0x%x size=%d\n",
GET_PC(infoPtr), infoPtr+sizeof(AdminInfo),
GET_ORIG_SIZE(infoPtr));
} else {
(*memPrintProc)(memPrintData,
"Mem_Free: PC=0x%x addr=0x%x size=%d *\n",
curPC, infoPtr+sizeof(AdminInfo), GET_ORIG_SIZE(infoPtr));
}
}
#endif /* MEM_TRACE */